----------------------------
-- Signed and Unsigned multiplier (implemented together)
--
-- UNSIGNED MULTIPLICATION 
-- 1) Split into 16x16 blocks
-- 2) Multiply each pair (used 16x16 hardware multipliers)
-- 3) Addition of partial products to be done in a pipelined fashion
-- 4) Total of 4+1 = 5 clock cycles
-- *** Notice the adder is essentially idle in the first stage.
-- This will be useful in the implementation of signed multiplication ***
--
-- SIGNED MULTIPLICATION
-- THE KEY HERE IS TO NOT CONSUME EXTRA CLOCKS!
-- Suppose terms are A and B. Based on mathematics, we have - 
-- Case 1) Both A and B are non-negative 
-- Revert back to unsigned version.

-- Case 2) Either A and B are zero
-- Answer is 0.

-- Case 3) Both A and B are negative
-- Then, A*B = (A\ + 1) * (B\ + 1), where X\ denotes the complement of X
-- That is, we take the absolute value (2's complement) of A and B and multiply them together
-- Hence, by expanding, we get A\B\ + A\ + B\ + 1
-- We multiply A\B\ as per normal.
-- for (A\ + B\ + 1), this can be computed in the first stage of the pipeline, where we force feed A\, B\ and 1 (via the carry input)
-- Hence, we require no extra clock cycles!

-- Case 4) WLOG, A is negative and B is STRICTLY positive (although hardware wise, this is troublesome....)
-- A * B = ((A\ + 1) * B)\ + 1
-- That is, we take the absolute value of A, multiply by B, and then invert again
-- By expanding this, we have (A\ * B + B)\ + 1 = (A\ * B + B - 1)\
-- Notice that B-1 will never need to borrow, since we have stated explicity that B is positive
-- Hence, we can compute A\*B as normal, and stuff B-1 into the first stage of the adder's pipeline
-- The end result has to be NOT'ted to obtain the answer
-- No extra clock cycles required!
--
-- chunkai, huang da, and tai === 3 unhappy engineers =(
-- 7 Oct, few more days left!
------------------------------

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity Unsigned_32x32_mult is
	PORT (
	clk	:	in	STD_LOGIC;
	arg1 	: 	in STD_LOGIC_VECTOR (31 downto 0);
	arg2 	: 	in STD_LOGIC_VECTOR (31 downto 0);
	start_signal : in STD_LOGIC;
	result : out STD_LOGIC_VECTOR (63 downto 0);
	is_signed : IN STD_LOGIC
	);
		
end Unsigned_32x32_mult;

--- Splits into blocks of 16, then does 16x16 block multiplication + adding
--- Within multiplication, pipelined
architecture Blocks_Of_16 of Unsigned_32x32_mult is
	
	-- Computes B-1 or A\+B\+1
	component mult_main_adder_with_control is
	PORT (
		A : in STD_LOGIC_VECTOR(31 downto 0); -- original digits
		B : in STD_LOGIC_VECTOR(31 downto 0); -- original digits
		
		mult_in : in STD_LOGIC_VECTOR(31 downto 0);
		latch_in : in STD_LOGIC_VECTOR(31 downto 0);
		
		-- 00 => get from normal places (latch and feedback)
		-- 01 => get from A-1 (Since B is negative)
		-- 10 => get from B-1 (Since A is negative)
		-- 10 => get from B\ + A\ + 1
		CONTROL : in STD_LOGIC_VECTOR(1 downto 0);
		
		ADDER_RESULT_OUT : out STD_LOGIC_VECTOR(31 downto 0);
		ADDER_CARRY_OUT : out STD_LOGIC
		);
	end component;
	
	component mux_gen_4_wide is
		generic (bus_size : integer);
		PORT (
		i0 : in STD_LOGIC_VECTOR(bus_size-1 downto 0);
		i1 : in STD_LOGIC_VECTOR(bus_size-1 downto 0);
		i2 : in STD_LOGIC_VECTOR(bus_size-1 downto 0);
		i3 : in STD_LOGIC_VECTOR(bus_size-1 downto 0);
		
		selector : in STD_LOGIC_VECTOR(1 downto 0);
		output : out STD_LOGIC_VECTOR(bus_size-1 downto 0);
		neg : in STD_LOGIC
		);
		
	end component;
	
	component multiplier_16x16 is
		PORT (
			op1 : in STD_LOGIC_VECTOR(15 downto 0);
			op2 : in STD_LOGIC_VECTOR(15 downto 0);
			res : out STD_LOGIC_VECTOR(31 downto 0)
		);

	end component;
	
	component latch_32b is
		PORT (
		clk : in STD_LOGIC;
		latch_in : in STD_LOGIC_VECTOR(31 downto 0);
		latch_out : out STD_LOGIC_VECTOR(31 downto 0)
		);
	end component;
	
	component latch_shifter is
	PORT (
		output : out STD_LOGIC_VECTOR (63 downto 0);
		load_control : in STD_LOGIC; -- to load zeros into higher 32 bits, or to take from adder
		shift_control : in STD_LOGIC;
		adder_in : in STD_LOGIC_VECTOR (31 downto 0);
		adder_carry : in STD_LOGIC;
		clk : in STD_LOGIC;
		disable_changes : in STD_LOGIC
	);
	end component;
	
	component mult_32x32_control is
		PORT (
		clk : IN STD_LOGIC;
		start : IN STD_LOGIC;
		selection1 : OUT STD_LOGIC_VECTOR(1 downto 0);
		selection2 : OUT STD_LOGIC_VECTOR(1 downto 0);
		shift_control : OUT STD_LOGIC;
		clear_latch : OUT STD_LOGIC;
		disable_changes : OUT STD_LOGIC;
		is_signed : IN STD_LOGIC;
		
		-- signs of the 2 numbers, +ve is negative
		sign1 : IN STD_LOGIC;
		sign2 : IN STD_LOGIC;
		
		-- 
		adder_control : OUT STD_LOGIC_VECTOR(1 downto 0)
		);
	end component;

	
	------------------------------------------------------
	--- SIGNALS
	------------------------------------------------------
	
	signal a1, a0, b1, b0 : STD_LOGIC_VECTOR(15 downto 0);
	
	--- Control unit selections
	signal selection1 : STD_LOGIC_VECTOR(1 downto 0);
	signal selection2 : STD_LOGIC_VECTOR(1 downto 0);
	
	--- MUX block selector
	signal selected1 : STD_LOGIC_VECTOR(15 downto 0); --- mux1 selected
	signal selected2 : STD_LOGIC_VECTOR(15 downto 0); --- mux2 selected
	
	--- multiplier output
	signal mult_output : STD_LOGIC_VECTOR(31 downto 0);
	
	--- multiplier-temporary_latch 
	signal mult_storage : STD_LOGIC_VECTOR(31 downto 0);
	
	--- adder output
	signal adder_main_output : STD_LOGIC_VECTOR(31 downto 0);
	signal adder_main_carry_out : STD_LOGIC;
	
	--- adder control in
	signal adder_control_in : STD_LOGIC_VECTOR(1 downto 0);
	
	--- latch output
	signal latch_output : STD_LOGIC_VECTOR(63 downto 0);

	--- control signals
	signal shift_ctrl : STD_LOGIC;
	signal load_ctrl : STD_LOGIC;
	signal disable_latch_changes : STD_LOGIC;

	--- signal to obtain output in just 5 cycles.
	signal fast_output : STD_LOGIC_VECTOR(63 downto 0);
	
begin
	
	a1 <= arg1(31 downto 16);
	a0 <= arg1(15 downto 0);
	b1 <= arg2(31 downto 16);
	b0 <= arg2(15 downto 0);
	
	fast_output <= adder_main_output&latch_output(31 downto 0);
	
	--- Selector for 1st operand to 16x16 multiplier
	block_selector1 : mux_gen_4_wide
	generic map(bus_size => 16)
	port map(i0 => a0,
				i1 => a1,
				i2 => b0,
				i3 => b1,
				selector => selection1,
				output => selected1,
				neg => is_signed);
				
	--- Selector for 2nd operand to 16x16 multiplier
	block_selector2 : mux_gen_4_wide
	generic map(bus_size => 16)
	port map(i0 => a0,
				i1 => a1,
				i2 => b0,
				i3 => b1,
				selector => selection2,
				output => selected2,
				neg => is_signed);
	
	--- Main pipelined multiplier
	main_multiplier : multiplier_16x16
	PORT map (
		op1 => selected1,
		op2 => selected2,
		res => mult_output
	);
	
	--- 32b storage for 1-cycle temp storage of multiplier's result
	mult_temp_res : latch_32b
	PORT map (
		clk => clk,
		latch_in => mult_output,
		latch_out => mult_storage
	);
	
	--- Main pipelined adder
	-- Computes B-1 or A\+B\+1
	main_adder : mult_main_adder_with_control
	PORT MAP (
		A => arg1,
		B => arg2,
		
		mult_in => mult_storage,
		latch_in => latch_output(63 downto 32),
		
		CONTROL => adder_control_in,


		ADDER_RESULT_OUT => adder_main_output,
		ADDER_CARRY_OUT => adder_main_carry_out
	);
	
	
	main_latch : latch_shifter 
		PORT map (
			output => latch_output,
			load_control => load_ctrl,-- to load zeros into higher 32 bits, or to take from adder
			shift_control => shift_ctrl,
			adder_in => adder_main_output,
			adder_carry => adder_main_carry_out,
			clk => clk,
			disable_changes => disable_latch_changes
			
	);
	
	ctrl : mult_32x32_control
		PORT map (
			clk => clk,
			start => start_signal,
			selection1 => selection1,
			selection2 => selection2,
			shift_control => shift_ctrl,
			clear_latch => load_ctrl,
			disable_changes => disable_latch_changes,
			is_signed => is_signed,
			
			sign1 => arg1(31),
			sign2 => arg2(31),
			
			adder_control => adder_control_in
			
		);

	--result <= X"0000000000000000" when (arg1 = X"00000000" or arg2 = X"00000000") else -- if either guy is 0, just output 0
	--			 latch_output when (arg1(31) = arg2(31)) or is_signed = '0' else -- if both are same sigh (both +ve or -ve, then no need to invert)
	--			 not latch_output; -- invert output
	
	result <= X"0000000000000000" when (arg1 = X"00000000" or arg2 = X"00000000") else -- if either guy is 0, just output 0
				 fast_output when (arg1(31) = arg2(31)) or is_signed = '0' else -- if both are same sigh (both +ve or -ve, then no need to invert)
				 not fast_output; -- invert output

end Blocks_Of_16;

